package chc

import (
	"context"
	"fmt"
	"reflect"
	"strings"
	"time"
)

type QuerySet struct {
	mi        *modelInfo
	cond      *Condition
	related   []string
	relDepth  int
	limit     int64
	offset    int64
	groups    []string
	orders    []string
	distinct  bool
	forupdate bool
	// orm        *orm
	ctx        context.Context
	forContext bool
}

func queryTable(ptrStructOrTableName interface{}) (qs *QuerySet) {
	var name string
	if table, ok := ptrStructOrTableName.(string); ok {
		name = nameStrategyMap[defaultNameStrategy](table)
		if mi, ok := modelCache.get(name); ok {
			qs = newQuerySet(mi)
		}
	} else {
		name = getFullName(indirectType(reflect.TypeOf(ptrStructOrTableName)))
		if mi, ok := modelCache.getByFullName(name); ok {
			qs = newQuerySet(mi)
		}
	}

	if nil == qs {
		panic(fmt.Errorf("<chc.QueryTable> table name: `%s` not exists", name))
	}

	return qs
}

func newQuerySet(mi *modelInfo) (qs *QuerySet) {
	qs = new(QuerySet)
	qs.mi = mi
	return qs
}

// set offset number
func (qs *QuerySet) setOffset(num interface{}) {
	qs.offset = ToInt64(num)
}

func (qs *QuerySet) Filter(expr string, args ...interface{}) *QuerySet {
	if nil == qs.cond {
		qs.cond = NewCondition()
	}
	qs.cond = qs.cond.And(expr, args...)
	return qs
}

func (qs *QuerySet) FilterRaw(expr string, sql string) *QuerySet {
	if nil == qs.cond {
		qs.cond = NewCondition()
	}
	qs.cond = qs.cond.Raw(expr, sql)
	return qs
}

func (qs *QuerySet) Exclude(expr string, args ...interface{}) *QuerySet {
	if nil == qs.cond {
		qs.cond = NewCondition()
	}
	qs.cond = qs.cond.AndNot(expr, args...)
	return qs
}

func (qs *QuerySet) Limit(limit interface{}, args ...interface{}) *QuerySet {
	qs.limit = ToInt64(limit)
	if len(args) > 0 {
		qs.setOffset(args[0])
	}
	return qs
}

func (qs *QuerySet) Offset(offset interface{}) *QuerySet {
	qs.setOffset(offset)
	return qs
}

func (qs *QuerySet) GroupBy(exprs ...string) *QuerySet {
	qs.groups = exprs
	return qs
}

func (qs *QuerySet) OrderBy(exprs ...string) *QuerySet {
	qs.orders = exprs
	return qs
}

func (qs *QuerySet) Distinct() *QuerySet {
	qs.distinct = true
	return qs
}

func (qs *QuerySet) ForUpdate() *QuerySet {
	qs.forupdate = true
	return qs
}

// set condition to QuerySeter.
func (qs *QuerySet) SetCond(cond *Condition) *QuerySet {
	qs.cond = cond
	return qs
}

// get condition from QuerySeter
func (qs *QuerySet) GetCond() *Condition {
	return qs.cond
}

func (qs *QuerySet) Count() (int64, error) {
	return dbCount(qs, qs.mi, DefaultTimeLoc)
}

func (qs *QuerySet) Exist() bool {
	return false
}

func (qs *QuerySet) Delete() (int64, error) {
	return 0, nil
}

func (qs *QuerySet) Update(values Params) (int64, error) {
	return 0, nil
}

func (qs *QuerySet) All(container interface{}, cols ...string) (int64, error) {
	return dbReadBatch(qs, qs.mi, qs.cond, container, DefaultTimeLoc, cols)
}

func (qs *QuerySet) One(container interface{}, cols ...string) error {
	return nil
}

func (qs *QuerySet) GetCondSQL(cond *Condition, tz *time.Location) (where string, params []interface{}) {

	if nil == cond {
		return
	}

	for i, p := range cond.params {
		if i > 0 {
			if p.isOr {
				where += "OR "
			} else {
				where += "AND "
			}
		}
		if p.isNot {
			where += "NOT "
		}
		if p.isCond {
			w, ps := qs.GetCondSQL(p.cond, tz)
			if w != "" {
				w = fmt.Sprintf("( %s) ", w)
			}
			where += w
			params = append(params, ps...)
		} else {
			exprs := p.exprs

			num := len(exprs) - 1
			operator := ""
			if operators[exprs[num]] {
				operator = exprs[num]
				exprs = exprs[:num]
			}

			index, _, fi, suc := parseExprs(qs.mi, exprs)
			if !suc {
				panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(p.exprs, ExprSep)))
			}

			if operator == "" {
				operator = "exact"
			}

			var operSQL string
			var args []interface{}
			if p.isRaw {
				operSQL = p.sql
			} else {
				operSQL, args = GenerateOperatorSQL(qs.mi, fi, operator, p.args, tz)
			}

			leftCol := fmt.Sprintf("%s.%s%s%s", index, FieldQuote, fi.column, FieldQuote)
			GenerateOperatorLeftCol(fi, operator, &leftCol)

			where += fmt.Sprintf("%s %s ", leftCol, operSQL)
			params = append(params, args...)

		}
	}

	if where != "" {
		where = "WHERE " + where
	}

	return
}

func (qs *QuerySet) GetGroupBySQL(groups []string) string {
	if len(groups) == 0 {
		return ""
	}

	groupSqls := make([]string, 0, len(groups))
	for _, group := range groups {
		exprs := strings.Split(group, ExprSep)

		_, _, fi, suc := parseExprs(qs.mi, exprs)
		if !suc {
			panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(exprs, ExprSep)))
		}

		groupSqls = append(groupSqls, fmt.Sprintf("%s%s%s", FieldQuote, fi.column, FieldQuote))
	}

	return fmt.Sprintf("GROUP BY %s ", strings.Join(groupSqls, ", "))
}

func (qs *QuerySet) GetOrderBySQL(orders []string) string {
	if len(orders) == 0 {
		return ""
	}

	orderSqls := make([]string, 0, len(orders))
	for _, order := range orders {
		asc := "ASC"
		if order[0] == '-' {
			asc = "DESC"
			order = order[1:]
		}
		exprs := strings.Split(order, ExprSep)

		_, _, fi, suc := parseExprs(qs.mi, exprs)
		if !suc {
			panic(fmt.Errorf("unknown field/column name `%s`", strings.Join(exprs, ExprSep)))
		}

		orderSqls = append(orderSqls, fmt.Sprintf("%s%s%s %s", FieldQuote, fi.column, FieldQuote, asc))
	}

	return fmt.Sprintf("ORDER BY %s ", strings.Join(orderSqls, ", "))
}

func (qs *QuerySet) GetLimitSQL(offset int64, limit int64) (limits string) {

	if limit == 0 {
		limit = int64(DefaultRowsLimit)
	}
	if limit < 0 {
		// no limit
		if offset > 0 {
			if MaxLimit == 0 {
				limits = fmt.Sprintf("OFFSET %d", offset)
			} else {
				limits = fmt.Sprintf("LIMIT %d OFFSET %d", MaxLimit, offset)
			}
		}
	} else if offset <= 0 {
		limits = fmt.Sprintf("LIMIT %d", limit)
	} else {
		limits = fmt.Sprintf("LIMIT %d OFFSET %d", limit, offset)
	}
	return
}
